![]() |
![]() |
|
Der Anstoß dazu kann auch im Programmcode unter Aufruf der statischen Methode Collect der Klasse GC gegeben werden:
Die Suche nach allen freigegebenen Objekten nimmt natürlich Zeit in Anspruch und führt zu Performanceverlusten der laufenden Anwendung. Daher ist es nicht empfehlenswert, ohne ein gewichtiges Argument bei jeder Objektzerstörung »zur Sicherheit« zusätzlich den Bereinigungsprozess zu aktivieren. Die Finalize-MethodeDer Garbage Collector ist als Speicherbereinigungsprozess für die Freigabe der lokalen Ressourcen verantwortlich. Er ist allerdings nicht in der Lage, die Aufräumarbeiten anzustoßen, die von fremden Rechnern für das zu zerstörende Objekt bereitgestellt werden. Was ist darunter zu verstehen? Wird ein Objekt beispielsweise dazu benutzt, eine Datenbankverbindung zu einer Datenbank wie dem Microsoft SQL Server zu öffnen, enthält nicht nur das Objekt Verbindungsinformationen, sondern auch der SQL Server reserviert seinerseits Ressourcen für die geöffnete Verbindung. Versäumen Sie, dem SQL Server mitzuteilen, dass das Objekt zerstört und die Verbindung nicht weiter beansprucht wird, werden die vom Objekt belegten Ressourcen für die Verbindung zwar nach dessen Zerstörung freigegeben, nicht aber die, die der SQL Server dafür bereitstellt. Im Extremfall, wenn sehr viele Clients die Dienste der Datenbank in Anspruch nehmen und die Verbindung nicht ordentlich schließen, kann das serverseitig zu Problemen führen, da die Gesamtanzahl der Zugriffe nicht unbegrenzt ist. Da der GC nur den lokalen Speicher bereinigt, muss es eine andere Möglichkeit geben, um auch die entfernten Ressourcen bei der Zerstörung freizugeben oder zumindest den Anstoß dazu zu geben. Letztendlich muss der Entwickler selbst für diese Aktion sorgen und den entsprechenden Code dafür schreiben. Es gibt eine Methode, die der Garbage Collector kennt und – soweit implementiert – ausführt. Diese heißt Finalize. Hat der Aufräummechanismus ein Objekt entdeckt, das nicht mehr referenziert wird, sucht er in diesem Objekt nach der Methode Finalize. Nicht jedes Objekt muss zwangsläufig diese Methode bereitstellen, aber wenn sie vorhanden ist, wird sie kurz vor der endgültigen Zerstörung des Objekts noch ausgeführt. Die Definition mit
sieht im ersten Moment für Sie wegen des Modifizierers Overrides ungewöhnlich aus, hängt aber mit dem Vererbungskonzept zusammen und darf daher nicht anders lauten. In Kapitel 6 werden wir uns mit der Vererbung beschäftigen, nehmen Sie daher Overrides einfach als notwendig hin. Innerhalb der Methode stehen alle notwendigen Anweisungen zur Verfügung, um den entfernten Rechner mit den Informationen zu versorgen, dass er in Eigeninitiative die erforderlichen Maßnahmen ergreift. Die Anweisung
ist immer die letzte Anweisung innerhalb von Finalize und ruft die gleichnamige, geerbte Methode der direkten Basisklasse auf. Die Referenz MyBase verweist auf die Basisklasse, ähnlich wie Me der Verweis eines Objekts auf sich selbst darstellt. Vor der Zerstörung eines freigegebenen Objekts prüft der Garbage Collector zunächst, ob die Klasse des Objekts Finalize bereitstellt. Wenn ja, wird sie ausgeführt und im Anschluss daran der vom Objekt reservierte Speicher wieder freigegeben. Die natürliche Folge dieses Arbeitsaufwands ist ein gewisser Performanceverlust der Anwendung. Daher sollten Sie sich immer an die folgende Regel halten:
Der Garbage Collector in AktionNun wollen wir natürlich einmal den Garbage Collector bei seiner Arbeit beobachten. Dazu wird im folgenden Beispiel die Klasse GCTestClass definiert, welche die Finalize-Methode implementiert und bei deren Aufruf eine Meldung an der Konsole ausgegeben wird, die eine interne Kennnummer enthält.
Innerhalb des Moduls sind drei verschiedene Objektvariablen deklariert: eine globale (obj1) sowie zwei lokale (obj2 und obj3). Damit lässt sich für jedes Objekt eine unterschiedliche Lebensdauer festlegen: obj1 wird zerstört, wenn die Anwendung beendet wird, obj2 nach dem Verlassen der Methode MyProc. Die Zerstörung von obj3 wird mit Setzen auf Nothing angestoßen. Um den Garbage Collector auf jeden Fall zur Aktivität anzuspornen, wird in Main die Methode Collect der Klasse GC aufgerufen. Wenn Sie die Anwendung starten, werden Sie nach dem ersten Drücken der (Enter)-Taste die Collect-Methode aufrufen und die Meldung der beiden Finalisierer der Objekte obj2 und obj3 sehen. Die Meldung des letzten Objekts können Sie nach einer weiteren Betätigung der (Enter)-Taste noch kurz erkennen, bevor das Konsolenfenster geschlossen wird. Die »Dispose«-MethodeEin unter Umständen nicht akzeptabler Nachteil ist mit der Finalize-Methode verbunden: Wenn sie implementiert ist kann nicht exakt vorherbestimmt werden, wann sie zur Ausführung kommt. Wie Sie bereits wissen, werden die Aufräumarbeiten dann angestoßen, wenn durch die Beschäftigungslosigkeit einer laufenden Anwendung der niedrigprioritäre Thread des Garbage Collectors seine Arbeit aufnimmt oder sich die Speicherressourcen verknappen. Tatsächlich sind sogar Situationen denkbar, die niemals zu einer Ausführung von Finalize führen – denken Sie nur an den Absturz des Rechners. Folgerichtig kann auch nicht garantiert werden, dass der GC überhaupt jemals einmal seine Aufgabe verrichtet. Wenn ein Objekt aber kostspielige oder begrenzte Ressourcen beansprucht, muss sichergestellt werden, dass diese so schnell wie möglich wieder freigegeben werden – eine Garantie, die Finalize nicht gibt. Andererseits kann die durch den Zugriffsmodifizierer Protected geschützte Finalize-Methode auch nicht von einem Client aufgerufen werden, um die Ausführung zu erzwingen, denn Protected ist für den Aufrufer gleichbedeutend mit Private. Um diesem Problem zu begegnen, können Sie eine öffentliche Methode implementieren, die ein Client aufrufen kann. Diese Methode heißt Dispose. Damit aber nicht genug. Wir müssen im objektorientierten Sinne noch einen Riesenschritt nach vorne machen und unsere Klasse um die Schnittstelle IDisposable erweitern. Eine Schnittstelle ist einer Klasse sehr ähnlich, jedoch sind andere Richtlinien damit verbunden, die sowohl die Implementierung als auch den Nutzen einer Schnittstelle beschreiben. Wir werden uns diesen in Kapitel 6 zuwenden, auf das Verständnis der Wirkungsweise der Dispose-Methode haben sie keinen Einfluss. Schauen wir uns zunächst den strukturellen Rahmen der Dispose-Methode an. Er lautet:
In der Definition der Klasse ClassA muss die Veröffentlichung der Schnittstelle IDisposable mit dem Schlüsselwort Implements bekannt gegeben werden. Jede Methode, die von einer Schnittstelle veröffentlicht wird, muss von der Klasse, die sich ihrerseits der Dienste der Schnittstelle bedient, implementiert werden. IDisposable enthält nur die Methode Dispose. Unsere Klasse ClassA übernimmt konventionsgemäß die Methode Dispose, indem hinter der Parameterliste noch einmal angegeben wird, aus welcher Schnittstelle die Methode stammt. Die Dispose-Methode soll dem Client die Sicherheit geben, nicht auf den GC und damit auf die Ausführung der Finalize-Methode warten zu müssen. Also muss der Code, der die beanspruchten Eigen- oder Fremdressourcen freigibt, in der Methode Dispose implementiert werden. Zwischen den beiden Methoden besteht ein gravierender Unterschied, der die weitere Vorgehensweise bei der Programmierung nachhaltig beeinflusst: Während Finalize automatisch aufgerufen wird, setzt die Ausführung von Dispose den expliziten Aufruf durch den Benutzer voraus:
Die Garantie, dass der Benutzer den Aufruf startet, kann aber niemand geben. Daran muss bei der Entwicklung einer Klasse gedacht werden, deren Objekte bei der Zerstörung fremde Ressourcen in Anspruch nehmen. Es gibt nur eine logische Schlussfolgerung, um diesem Umstand zu begegnen: Der Freigabecode muss sowohl in Dispose als auch in Finalize implementiert werden. Es bietet sich dazu an, aus der Dispose-Methode heraus die Finalize-Methode aufzurufen, die den Freigabecode enthält. Seien Sie sich jedoch darüber bewusst: Der explizite Aufruf von Finalize führt nicht dazu, dass der vom Objekt reservierte Speicherbereich aufgegeben wird!
Ein Problem dieses Codefragments ist, dass nicht nur im Falle des Aufrufs von Dispose die Methode Finalize ausgeführt wird, sondern auch dann, wenn der Garbage Collector zu einem späteren Zeitpunkt seine Arbeit verrichtet. Werden die Ressourcen aber zweimal freigegeben, führt das zu einem undefinierten Programmzustand. Um dem zu begegnen, muss die Methode SuppressFinalize der Klasse GC aufgerufen werden. SuppressFinalize stellt unter Übergabe einer Objektreferenz – in unserem Fall der Me-Referenz – sicher, dass der GC Finalize kein zweites Mal aufruft.
Zum Abschluss unserer Betrachtungen müssen noch zwei Situationen berücksichtigt werden, die im Zusammenhang mit der Implementierung von Dispose während der Laufzeit auftreten können und entsprechend behandelt werden müssen:
Im folgenden Codefragment wird gezeigt, wie Sie diesen Problemen begegnen können. Dazu wird eine fiktive Klasse Connection instanziiert, von der wir annehmen, dass sie eine Ressource auf einem entfernten Rechner reserviert.
In Finalize wird die Referenz des Objekts vom Typ Connection auf Nothing gesetzt. Weil die Aufräumarbeiten auf verschiedene Weisen angestoßen werden können, muss ein Element definiert werden, um den aktuellen Stand nachzuvollziehen. Hierzu dient die boolesche Variable bolDisposed. Ihr Zustand True kennzeichnet die bereits freigegebene Ressource. Betrachten wir nun den Ablauf zur Laufzeit und da zunächst den Fall, dass der Benutzer die Methode Dispose eines ClassA-Objekts zum ersten Mal aufruft. Zunächst wird der Inhalt des Flags bolDisposed geprüft. Ist er False, kommt es zur Ausführung von Finalize, was die Freigabe der Ressource anstößt und das Flag True setzt. Nachdem auch die Finalisierer der Basisklassen durchlaufen sind, wird der Programmablauf mit dem Methodenaufruf SuppressFinalize beendet. Das ist der normale Ablauf. Nun könnte der Benutzer noch ein weiteres Mal auf die Idee kommen, Dispose auszuführen. Dabei gehen wir davon aus, dass der GC zwischenzeitlich das Objekt noch nicht der endgültigen Zerstörung zugeführt hat. Die Prüfung des Flags bolDisposed, dessen Inhalt nun True ist, wird durch die bedingte Anweisung zu einer Abweisung führen. Betrachten wir nun noch den dritten Fall. Die im obigen Codefragment der Klasse implementierte Methode DoSomething symbolisiert eine Funktionalität, welche die Fremdressource in Anspruch nimmt. Wird DoSomething aufgerufen, nachdem es keine gültige Referenz auf das Objekt myRes mehr gibt, ist die Folge ein Laufzeitfehler, der zu einer Beendigung der laufenden Anwendung führt. Um dies zu vermeiden, muss in der Methode eine Ausnahmebehandlung vorgesehen werden, die ein weiteres Arbeiten mit der Anwendung gestattet. Mit der Implementierung der Ausnahmebehandlung werden wir uns jedoch erst in Kapitel 9 eingehend beschäftigen. Automatischer Aufruf der Methode »Dispose« durch »Using«Neu in Visual Basic 2005 ist, dass der Aufruf der Methode Dispose auch automatisch erfolgen kann. Dazu wird ein mit dem neuen Schlüsselwort Using definierter Anweisungsblock ins Leben gerufen. Dem Schlüsselwort Using folgt die Instanziierung einer Klasse, welche die Schnittstelle IDisposable implementiert und damit auch zwangsläufig Dispose bereitstellt. Mit dem Verlassen des Anweisungsblocks wird letztgenannte Methode implizit aufgerufen.
4.6.3 Zusammenfassung
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Objekte werden freigegeben, wenn sie ihren Gültigkeitsbereich verlassen oder die Referenz explizit auf Nothing gesetzt wird. Solchermaßen freigegebene Objekte werden noch nicht sofort zerstört. |
| Bei der Zerstörung eines Objekts durch den Garbage Collector wird der Speicherplatz, den das Objekt für sich beansprucht, automatisch freigegeben. |
| Wann der Garbage Collector in Aktion tritt, ist unbestimmt. Als Thread niedriger Priorität wird er nur dann aktiv, wenn die Anwendung beschäftigungslos ist oder die Speicherressourcen knapp werden. |
| Wird er ausgeführt, sucht der Garbage Collector alle unreferenzierten Objekte der aktuellen Anwendung. Danach erfolgt die Überprüfung, ob die Klassendefinitionen dieser Objekte eine Finalize-Methode implementieren, die dann gegebenenfalls vor der endgültigen Speicherplatzfreigabe ausgeführt wird. |
| Finalize dient dazu, die von einem Objekt beanspruchten Fremdressourcen freizugeben. Da Finalize vom Garbage Collector automatisch und zu einem nicht vorhersehbaren Zeitpunkt aufgerufen wird, kann in einer Klasse eine Methode implementiert werden, über welche die Ressourcenfreigabe gesteuert wird. Dazu veröffentlicht die Klasse die Schnittstelle IDisposable und übernimmt deren Methode Dispose. |
| Die Klasse GC stellt Methoden zur Verfügung, um den Speicherbereinigungsmechanismus zu steuern. Mit der Methode Collect wird die Speicherbereinigung angestoßen, mit der Methode SuppressFinalize ein Aufrufen des Finalisierers unterbunden. |
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.